package com.inepex.inechart.shared.linechart;

import java.util.ArrayList;
import java.util.TreeMap;

import com.inepex.inechart.client.Defaults;
import com.inepex.inechart.client.shape.Circle;
import com.inepex.inechart.client.shape.Rectangle;
import com.inepex.inechart.client.shape.Shape;
import com.inepex.inechart.shared.CartesianAssist;
import com.inepex.inechart.shared.properties.Color;
import com.inepex.inechart.shared.properties.LineProperties.LineStyle;
import com.inepex.inegraphics.shared.Context;
import com.inepex.inegraphics.shared.DrawingAreaAssist;
import com.inepex.inegraphics.shared.GraphicalObjectContainer;
import com.inepex.inegraphics.shared.gobjects.GraphicalObject;
import com.inepex.inegraphics.shared.gobjects.Path;
import com.inepex.inegraphics.shared.gobjects.PathElement;

public class LineChartToGO {

	protected final CartesianAssist cartesianAssist;

	public LineChartToGO(CartesianAssist cartesianAssist) {
		this.cartesianAssist = cartesianAssist;
	}

	protected static Context createFillContext(Color fillColor) {
		return new Context(fillColor.getAlpha(), Defaults.colorString, 0,
				fillColor.getColor(), 0, 0, Defaults.alpha,
				Defaults.colorString);
	}

	protected static Context createLineContext(Curve curve) {
		return new Context(
				curve.lineProperties != null ? curve.lineProperties.getLineColor().getAlpha() :	Defaults.alpha,
						curve.lineProperties != null ? curve.lineProperties.getLineColor().getColor() : curve.dataSet.getColor().getColor(),
								curve.lineProperties != null ? curve.lineProperties.getLineWidth() : Defaults.lineWidth,
										Defaults.colorString,
										curve.hasShadow ? curve.shadowOffsetX : 0,
												curve.hasShadow ? curve.shadowOffsetY : 0,
														curve.shadowColor == null ? Defaults.alpha : curve.shadowColor.getAlpha(),
																curve.shadowColor == null ? Defaults.colorString : curve.shadowColor.getColor());
	}

	protected ArrayList<Point> getCurvesPoints(Curve curve){
		return curve.points;
	}
	
	protected ArrayList<Point> getDiscontinuitiesAsPoints(Curve curve){
		return curve.discontinuitiesAsPoint;
	}
	
	public Path createFillPathForCurve(Curve curve, double fromCanvasX, double toCanvasX){
		Path path = null;
		double[] lastPair = null;
		double[] lastLineEnd = null;
		double rightEnd = toCanvasX;
		int botEnd = cartesianAssist.getBottomEnd();
		double width = toCanvasX - fromCanvasX;
		int height = cartesianAssist.getHeight();
		for(Point dataPoint : curve.points){ 
			double x = dataPoint.canvasX > rightEnd ? rightEnd : Math.max(dataPoint.canvasX, fromCanvasX);
			double y = dataPoint.canvasY > botEnd ? botEnd : (Math.max(dataPoint.canvasY, cartesianAssist.getPadding()[0])); 
			if(path == null){
				path = new Path(x, y, false, true, curve.zIndex, null);
			}
			else{
				double[] intersection = DrawingAreaAssist.getIntersection(
						lastPair[0], lastPair[1],
						dataPoint.canvasX, dataPoint.canvasY,
						fromCanvasX,
						cartesianAssist.getPadding()[0],
						width,
						height);
				if(intersection != null){
					if(lastLineEnd[0] != intersection[0] || lastLineEnd[1] != intersection[1]){
						path.lineTo(intersection[0], intersection[1], false);
					}
					path.lineTo(intersection[2], intersection[3], false);
				}
				path.lineTo(x, y, false);
			}
			lastPair = new double[]{dataPoint.canvasX, dataPoint.canvasY};
			lastLineEnd = new double[]{x,y};
		}
		return path;
	}
	
	public Path createStrokePathForCurve(Curve curve, double fromCanvasX, double toCanvasX){
		double[] lastDataPair = null;
		double[] lastLineEnd = null;
		boolean ready = false;
		Path path = null;
		double rightEnd = toCanvasX;
		double width = toCanvasX - fromCanvasX;
		int height = cartesianAssist.getHeight();
		for(Point dataPoint : curve.points){
			if(ready){
				break;
			}
			if(curve.dataSet.isSortable() && dataPoint.canvasX > rightEnd){
				ready =  true;
			}
			if(curve.discontinuitiesAsPoint.contains(dataPoint)){
				lastDataPair = null;
			}
			else{
				if(lastDataPair != null){
					double[] intersection = DrawingAreaAssist.getIntersection(
							lastDataPair[0], lastDataPair[1],
							dataPoint.canvasX, dataPoint.canvasY,
							fromCanvasX,
							cartesianAssist.getPadding()[0],
							width,
							height);
					if(intersection != null){
						if(path == null){
							path = new Path(intersection[0], intersection[1], true, false, curve.zIndex, null);
						}
						else if(lastLineEnd[0] != intersection[0] || lastLineEnd[1] != intersection[1]){
							path.moveTo(intersection[0], intersection[1], false);
						}
						path.lineTo(intersection[2], intersection[3], false);
						lastLineEnd = new double[]{intersection[2], intersection[3]};
					}
				}
				lastDataPair = new double[]{dataPoint.canvasX, dataPoint.canvasY};
			}

		}
		return path;
	}

	/**
	 * Creates line chart {@link GraphicalObject}s:
	 *  -lines,
	 *  -fills
	 *  and puts them into {@link #lineChartGOsPerCurve} container
	 * 
	 * @param curve
	 */
	protected GraphicalObjectContainer createLineChartGOs(Curve curve, TreeMap<Curve, Path> visiblePathPerCurve, Path fillPath) {
		GraphicalObjectContainer graphicalObjectContainer = new GraphicalObjectContainer();
		Path strokePath = visiblePathPerCurve.get(curve);
		if (curve.hasLine && strokePath != null) {
			Path line = new Path(strokePath);
			line.setContext(createLineContext(curve));
			line.setStroke(true);
			if (curve.lineProperties != null && curve.lineProperties.getStyle().equals(LineStyle.DASHED)){
				graphicalObjectContainer.add(DrawingAreaAssist.createDashedLine(line,
						curve.getLineProperties().getDashStrokeLength(), curve
						.getLineProperties().getDashDistance()));
			}
			else{
				graphicalObjectContainer.add(line);
			}
		}

		if(fillPath == null)
			return graphicalObjectContainer;
		if (curve.autoFill) {
			Path fill = new Path(fillPath);
			double minY = cartesianAssist.modelToCanvasVertical(cartesianAssist.getVerticalMin());
			fill.lineTo(fill.getLastPathElement().getEndPointX(), minY, false);
			fill.lineTo(fill.getBasePointX(), minY, false);
			fill.lineToBasePoint();
			fill.setFill(true);
			Color c;
			if (curve.getLineProperties() != null){
				c = curve.getLineProperties().getLineColor();
			}
			else {
				c = curve.dataSet.getColor();
			}
			fill.setContext(createFillContext(c));
			fill.getContext().setAlpha(Defaults.fillOpacity);
			graphicalObjectContainer.add(fill);
		}
		if (curve.toCurveFills != null && curve.toCurveFills.size() > 0) {
			for (Curve toCurve : curve.toCurveFills.keySet()) {
				Path fill = new Path(fillPath.getBasePointX(),
						fillPath.getBasePointY(), false,
						true,
						curve.getZIndex(), createFillContext(curve.toCurveFills.get(toCurve)));
				Path otherPath = visiblePathPerCurve.get(toCurve);

				if (otherPath == null)
					continue;
				for (PathElement e : fillPath.getPathElements()) {
					fill.lineTo(e.getEndPointX(), e.getEndPointY(), false);
				}
				for (int i = otherPath.getPathElements().size() - 1; i >= 0; i--) {
					PathElement e = otherPath.getPathElements().get(i);
					fill.lineTo(e.getEndPointX(), e.getEndPointY(), false);
				}
				fill.lineTo(otherPath.getBasePointX(),
						otherPath.getBasePointY(), false);
				fill.lineToBasePoint();
				graphicalObjectContainer.add(fill);
			}
		}
		if (curve.toYFills != null && curve.toYFills.size() > 0) {
			//TODO axis direction!!
			for (double y : curve.toYFills.keySet()) {
				Path fill = new Path(fillPath);
				double calculatedY =  cartesianAssist.modelToCanvasVertical(y);
				fill.lineTo(fill.getLastPathElement().getEndPointX(),calculatedY, false);
				fill.lineTo(fill.getBasePointX(), calculatedY, false);
				fill.lineToBasePoint();
				fill.setFill(true);
				fill.setzIndex(curve.getZIndex());
				fill.setContext(createFillContext(curve.toYFills.get(y)));
				graphicalObjectContainer.add(fill);
			}
		}
		return graphicalObjectContainer;
	}


	
	/**
	 * Creates point chart {@link GraphicalObject}s and puts them into
	 * 
	 * @param curve
	 */
	protected GraphicalObjectContainer createPointChartGOs(Curve curve) {
		GraphicalObjectContainer goc = new GraphicalObjectContainer();
		if(curve.points.size() == 0){
			return goc;
		}
		
		Shape normal = curve.pointShape;
		if(normal == null){
			//use defaults
			normal = Defaults.normalPoint();	
			normal.getProperties().getLineProperties().setLineColor(curve.dataSet.getColor());
		}
		if(normal != null){
			normal.setZIndex(curve.zIndex);
		}

		if(curve.hasShadow){
			if(normal != null){
				normal.setShadowColor(curve.shadowColor);
				normal.setShadowOffsetX(curve.shadowOffsetX);
				normal.setShadowOffsetY(curve.shadowOffsetY);
			}
		}

		for(Point point : curve.points){
			if(!point.isInViewport){
				continue;
			}
			goc.add(createPoint(curve, point, normal));
		}
		return goc;
	}

	/**
	 * 
	 * Creates a point represented by the given {@link Shape}
	 * @param curve
	 * @param point
	 * @param shape
	 * @return
	 */
	protected GraphicalObjectContainer createPoint(Curve curve, Point point, Shape shape){
		GraphicalObjectContainer goc = new GraphicalObjectContainer();
		if(shape != null){
			for(GraphicalObject go : shape.toGraphicalObjects()){
				if (shape instanceof Circle) {
					go.setBasePointX(point.canvasX);
					go.setBasePointY(point.canvasY);
				}
				else if (shape instanceof Rectangle) {
					go.setBasePointX(point.canvasX - ((Rectangle) shape).getWidth() / 2);
					go.setBasePointY(point.canvasY - ((Rectangle) shape).getHeight() / 2);
				}
				goc.add(go);
			}
		}
		return goc;
	}
}
